/**
 * 
 */
package evolution;

import java.util.ArrayList;
import java.util.TreeSet;
import ca.swank.hardquor.Move;
import ca.swank.hardquor.WalkMove;
import ca.swank.hardquor.WallMove;
import ca.swank.hardquor.hardquorBoard;
import ca.swank.hq.ai.CachingSmartBoard;

/**
 * @author alonseg
 * 
 */
public class Individual implements Cloneable {

	private static double GENERATE_WALL_MOVE_PROB = 0.1;
	private final int[] wallValues = new int[] { 0, 4, 7, 10, 13, 16, 19, 21, 23, 25, 26 };

	private double fitness;
	private Move[] moveSeq;
	private int length;
	private int turn;
	private CachingSmartBoard initialBoard;

	/**
	 * 
	 */
	public Individual(int length, int turn) {
		moveSeq = new Move[length];
		this.length = length;
		this.turn = turn;
	}

	public void generate(CachingSmartBoard board) {
		this.initialBoard = (CachingSmartBoard) board.clone();
		int i = 0;
		int tmpTurn = this.turn;
		while (i < this.length) {
			Move current = this.generateMove(board, tmpTurn);
			if (current == null)
				continue;
			moveSeq[i] = current;
			// System.out.println(board.toString());
			tmpTurn = 1 - tmpTurn;
			i++;
		}
	}

	public void mutate() {
		double totalRanks = ((this.length + 1) * (this.length)) / 2;
		int random = (int) ((double) Math.random() * (double) (totalRanks + 1));
		int i = 1;
		do {
			random -= i;
			i++;
		} while (random > 0);
		i = i - 2;

		// Create a board with the board position after the i-1 move has been
		// done
		CachingSmartBoard board = (CachingSmartBoard) this.initialBoard.clone();
		for (int j = 0; j < i; j++) {
			board.makeMove(this.moveSeq[j]);
		}

		// change the board including the i-th position with random moves
		for (int j = i; j < this.length; j++) {
			// make legal move
			Move m = this.generateMove(board, (j + this.turn) % 2);
			while (m == null) {
				m = this.generateMove(board, (j + this.turn) % 2);
			}
			this.moveSeq[j] = m;
		}
	}

	public double getFitness() {
		return fitness;
	}

	public Move[] getMoveSeq() {
		return moveSeq;
	}

	/**
	 * The move to be performed
	 * 
	 * @return first move in the sequence
	 */
	public Move getMove() {
		return this.moveSeq[0];
	}

	public int getTurn() {
		return turn;
	}

	public void setFitness(double fitness) {
		this.fitness = fitness;
	}

	public void setMoveSeq(Move[] moveSeq) {
		this.moveSeq = moveSeq;
	}

	public CachingSmartBoard getInitialBoard() {
		return initialBoard;
	}

	public void setInitialBoard(CachingSmartBoard initialBoard) {
		this.initialBoard = initialBoard;
	}

	@Override
	protected Object clone() {
		Individual in = new Individual(length, turn);
		Move[] tmpMoves = new Move[this.length];
		for (int i = 0; i < this.length; i++) {
			tmpMoves[i] = this.moveSeq[i];
		}
		in.fitness = this.fitness;
		in.setMoveSeq(tmpMoves);
		in.setInitialBoard(initialBoard);
		return in;
	}

	@Override
	public String toString() {
		String res = "";
		res += "[" + moveSeq[0];
		for (int i = 1; i < moveSeq.length; i++) {
			res += ", " + moveSeq[i].toString();
		}
		res += "] Fitness: " + this.fitness;
		return res;
	}

	private Move generateMove(CachingSmartBoard board, int tmpTurn) {
		Move res = null;
		int[] loc = board.getPlayerLocation(tmpTurn);
		if(board.getWinner() != hardquorBoard.PLAYER_NONE) {
			return new WalkMove(loc, loc);
		}
		boolean isWalkMove = Math.random() > GENERATE_WALL_MOVE_PROB;
		if (board.getPlayerWalls(tmpTurn) == 0) {
			isWalkMove = true;
		}
		if (isWalkMove) {
			TreeSet<int[]> legalMoves = board.getLegalMoves(loc);
			int ind = (int) ((double) Math.random() * (double) legalMoves
					.size());
			int[] tmpCurrent = legalMoves.first(); // Temporary init value
			legalMoves.remove(tmpCurrent);
			while (ind > 0) {
				tmpCurrent = (int[]) legalMoves.first();
				legalMoves.remove(tmpCurrent);
				ind--;
			}
			WalkMove current = new WalkMove(loc, tmpCurrent);
			res = current;
			board.makeMove(current);
		} else {
			ArrayList<WallMove> walls = board.getAllBlockingWalls(board.getPlayerLocation(1 - tmpTurn), 1 - tmpTurn);
			if(board.getPlayerWalls(1 - tmpTurn) > 0) {
				walls.addAll(board.getAllDefensiveWalls(board.getPlayerLocation(turn), turn, 2, wallValues));
			}
			if (walls.size() == 0)
				return null;
			int ind = (int) ((double) (Math.random() * (double) walls.size()));
			WallMove current = walls.get(ind);
			res = current;
			board.makeMove(current);
		}
		return res;
	}

}
